--- layout: post title: Get full Java EE 8 in Open Liberty 18.0.0.2 date: 2018-06-29 08:00:00 -0000 categories: blog author_picture: https://avatars3.githubusercontent.com/u/3322532 author_github: https://github.com/lauracowen seo-title: Get the full Java EE 8 implementation in Open Liberty 18.0.0.2 - OpenLiberty.io seo-description: Full Java EE 8 support, including JAX-RS 2.1 reactive client and server-sent events, CDI event-ordering and asynchonous events, HTTP/2 support in servlets, JSF 2.3, JPA 2.2, JWT cookies (yum!), security improvements,...oh, and you can now deploy Spring Boot applications to Liberty. blog_description: "Full Java EE 8 support, including JAX-RS 2.1 reactive client and server-sent events, CDI event-ordering and asynchonous events, HTTP/2 support in servlets, JSF 2.3, JPA 2.2, JWT cookies (yum!), security improvements,...oh, and you can now deploy Spring Boot applications to Liberty." --- = Get full Java EE 8 in Open Liberty 18.0.0.2 Laura Cowen :imagesdir: / :url-prefix: :url-about: / Yes, it's here! Open Liberty is the first app server (to our knowledge) with full Java EE 8 support. What does that mean? Well, we've got the goodness of JAX-RS 2.1 reactive client and server-sent events, CDI event-ordering and asynchonous events, HTTP/2 support in servlets, JSF 2.3 (for the JSF fans, you know who you are), JPA 2.2, JWT cookies (yum!), security improvements,...oh, and you can now deploy Spring Boot applications to Liberty #justsayin' What are you waiting for? Download Open Liberty 18.0.0.2: [link=https://openliberty.io/downloads/] image::img/blog/blog_btn_download-ol.svg[Download Open Liberty, align="center", role="download-ol-button"] [link=https://stackoverflow.com/tags/open-liberty] image::img/blog/blog_btn_stack.svg[Ask a question on Stack Overflow, align="center"] Alternatively, if you're using https://www.openliberty.io/guides/maven-intro.html[Maven], here are the coordinates: [source,xml] ---- io.openliberty openliberty-runtime 18.0.0.2 zip ---- Or for https://openliberty.io/guides/gradle-intro.html[Gradle]: [source,json] ---- dependencies { libertyRuntime group: 'io.openliberty', name: 'openliberty-runtime', version: '[18.0.0.2,)' } ---- Or if you're using Docker: [source] ---- docker pull open-liberty ---- In Open Liberty 18.0.0.2, you'll find: * <> ** <> ** <> ** <> ** <> ** <> ** <> ** <> ** <> ** <> ** <> * And: ** <> ** <> As we don't have a full set of documentation implemented for Open Liberty yet, the items below point to the https://www.ibm.com/support/knowledgecenter/SSEQTP_liberty/as_ditamaps/was900_welcome_liberty.html[official documentation for WebSphere Liberty] so you can find out more about them (WebSphere Liberty is built on Open Liberty). [#javaee8] == Full support for Java EE 8 https://openliberty.io/downloads/[Download Liberty] with either the Java EE 8 Web Profile runtime or the Java EE 8 Full Platform runtime. You can enable all of the Java EE 8 features (or the more lightweight set of Web Profile features) with a single feature in your `server.xml`. === Java EE 8 Web Profile The features included when you enable the `webProfile-8.0` feature (Java EE 8 Web Profile) and are new in Java EE 8 are: * `appSecurity-3.0` * `beanValidation-2.0` * `cdi-2.0` * `jaxrs-2.1` * `jaxrsClient-2.1` * `jpa-2.2` * `jpaContainer-2.2` * `jsf-2.3` * `jsonb-1.0` * `jsonp-1.1` * `servlet-4.0` The `webProfile-8.0` feature also contains the following features which are unchanged from Java EE 7: `distributedMap01.0`, `ejbLite-3.2`, `el-3.0`, `jaspic-1.1`, `jdbc-4.2`, `jndi-1.0`, `jsp-2.3`, `managedBeans-1.0`, `ssl-1.0`, `websocket-1.1`. To enable the whole of the `webProfile-8.0` feature, add the feature definition to your `server.xml`: [source,xml] ---- webProfile-8.0 ---- === Java EE 8 Full Profile The features included when you enable the `javaee-8.0` feature (Java EE 8 Full Platform) and are new in Java EE 8 are: * `webProfile-8.0` (see above) * `javaMail-1.6` The `javaee-8.0` feature also contains the following features which are unchanged from Java EE 7: `appClientSupport-1.0`, `batch-1.0`, `concurrent-1.0`, `ejb-3.2`, `ejbHome-3.2`, `ejbPersistentTimer-3.2`, `jacc-1.5`, `jaspic-1.1`, `jaxws-2.2`, `jca-1.7`, `jcaInboundSecurity-1.0`, `jms-2.0`, `mdb-3.2`, `wasJmsClient-2.0`, `wasJmsSecurity-1.0`, `wasJmsServer-1.0`. To enable the whole of the `javaee-8.0`, add the feature definition to your `server.xml`: [source,xml] ---- javaee-8.0 ---- For more info: * http://www.oracle.com/technetwork/java/javaee/tech/java-ee-8-3890673.html[Java EE 8 technologies list (Oracle)] [#jaxrs] == Reactive client and server-sent events with JAX-RS 2.1 JAX-RS 2.1 enables two exciting new technologies: reactive client and server-sent events. The reactive client takes full advantage of Java 8 lambda expressions to enable highly scalable multi-threaded clients. Server-sent events allow developers to send data asynchronously to multiple clients, either individually or by broadcasting to all in an efficient manner. Coding an asynchronous JAX-RS client was possible in JAX-RS 2.0 but the reactive client in 2.1 enables much more parallelism with much less code. Users can now kick off multiple client requests having each one react to the response from the server, potentially by making new asynchronous requests. Server-sent events was not possible using JAX-RS APIs prior to 2.1. Users wishing to send updates to remote clients using JAX-RS would need to rely on polling which is inefficient. Otherwise, they would need to rely on third-party implementations to provide SSE or SSE-like functionality. Now a JAX-RS resource can allow multiple clients to register for events - then send them on a schedule, randomly, at the request of other clients, with very little code. To enable JAX-RS 2.1, add the definition to your `server.xml`: [source,xml] ---- jaxrs-2.1 ---- For more info: * https://www.linkedin.com/pulse/my-favorite-part-jax-rs-21-implementers-view-j-andrew-mccright/[My favourite part of JAX-RS 2.1: An implementer's view] * https://www.ibm.com/support/knowledgecenter/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/twlp_dep_jaxrs21.html[JAX-RS 2.1 (Knowledge Center)] * https://www.ibm.com/support/knowledgecenter/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/cwlp_jaxrs21_behavior.html[Changes between JAX-RS 2.0 and JAX-RS 2.1 (Knowledge Center)] * https://jcp.org/en/jsr/detail?id=370[JAX-RS 2.1 spec] * https://jax-rs.github.io/apidocs/2.1/[JAX-RS 2.1 Javadoc] [#cdi] == Asynchronous events, event ordering, and more with CDI 2.0 CDI 2.0 provides the following support: * Activate Request Context - Some third-party framework developers might want to have their own request lifecycle and have a tight control of it without creating a custom context. Previously, it was not possible for application developers to activate Request Context. CDI 2.0 added this support so that some unnecessary custom Request Scoped creation can be avoided. * Event ordering and asynchronous events - Prior to CDI 2.0, it was not possible to order the event notification. In CDI 2.0, use `@Priority` to order the synchronous event notifications. In CDI 2.0, you can fire and observe asynchronous events. * Add Interceptor support to produced beans - CDI 2.0 adds interceptor support on a producer using InterceptionFactory so that the produced beans have interceptors applied. * Provide a number of annotation literals - CDI uses annotation literals in various places. Previously, you needed to create annotation literal classes for some built-in scopes or qualifiers. In CDI 2.0, the APIs have the annotation literal provided so that the annotation literal for some useful scopes or qualifiers are provided by the CDI APIs. For example, in order to find a Foo bean with the `Default` qualifier, before CDI 2.0 you needed to do the following: [source,java] ---- @Inject Instance foo; public Foo getFoo() { return instance.select(DefaultLiteral.INSTANCE).get(); } public class DefaultLiteral extends AnnotationLiteral implements Default { public static final DefaultLiteral INSTANCE = new DefaultLiteral(); private DefaultLiteral() {} } ---- In CDI 2.0, you can do the following: [source,java] ---- @Inject Instance foo; public Foo getFoo() { return instance.select(Default.Literal.INSTANCE).get(); } ---- As you can see, in CDI 2.0, you can use `Default.Literal.INSTANCE`` to get the annotation of `Default`, which is a lot simpler. * CDI SPI configurators - In CDI 1.x, using SPI is used to generate verbose and less elegant code. In CDI 2.0, the configurators solve this. These configurators are accessible in lifecycle container event when writing extensions. To enable the CDI 2.0 feature, add the feature definition to your `server.xml`: [source,xml] ---- cdi-2.0 ---- [#servlet40] == Write dynamic web applications with Servlet 4.0 Servlet 4.0 is the latest Java EE 8 version of the Servlet specification. The `servlet-4.0` feature includes the new Servlet 4.0 features and functions, for example: * Support for HTTP/2 push/promise. * Support for HTTP trailers. * `HttpServletRequest.getServletMapping()` * `ServletContext.getSessionTimeout() and setSessionTimeout()` * `ServletContext.addJspFile()` * Support for new elements in `web.xml`: ** `default-context-path` ** `request-character-encoding` ** `response-character-encoding` To enable the Servlet 4.0 feature, add the feature definition to your `server.xml`: [source,xml] ---- servlet-4.0 ---- For more info: * https://www.ibm.com/support/knowledgecenter/en/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/twlp_config_servlet40.html[Servlet 4.0 (Knowledge Center)] * https://javaee.github.io/servlet-spec/[Java Servlet spec] [#http2] == HTTP/2 protocol support HTTP/2 is an optimization of the HTTP/1.1 protocol. Use of the HTTP/2 protocol is initiated by the client and accepted by the server. Web applications that involve numerous HTTP/1.1 sessions per webpage can see a significant performance improvement by opting into HTTP/2. Much of the optimization is achieved by allowing multiple HTTP/1.1 sessions to be transacted in parallel over one initial upgraded HTTP/1.1 connection. Secure HTTP/2 (h2) uses ALPN (Application-Layer Protocol Negotiation) to upgrade the protocol of an HTTP/1.1 session to HTTP/2. Insecure HTTP/2 (h2c) can be negotiated via an HTTP/1.1 Upgrade header. The HTTP/2 protocol then allows for full-duplex communication of HTTP/1.1 traffic between client and server over this one upgraded connection. Both client and server have to opt into the HTTP/2 protocol with the ALPN handshake being initiated by the client. Servlet 4.0 makes use of the HTTP/2 protocol to implement the Servlet 4.0 Server Push APIs, and HTTP/2 is enabled when the Servlet 4.0 Liberty feature is enabled. If the Servlet 3.1 Liberty feature is enabled instead of Servlet 4.0, HTTP/2 is off by default but can be enabled by setting the `protocolVersion = "http/2"` attribute of the `httpEndpoint` element. For more info, see: * https://www.ibm.com/support/knowledgecenter/en/SSEQTP_liberty/com.ibm.websphere.wlp.nd.multiplatform.doc/ae/cwlp_servlet40_http2.html[HTTP/2 in Servlet 4.0 (Knowledge Center)] * https://www.ibm.com/support/knowledgecenter/en/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/cwlp_alpnsupport.html[ALPN support (Knowledge Center)] * https://tools.ietf.org/html/rfc7540[HTTP protocol specification] * https://jcp.org/en/jsr/detail?id=369[Servlet 4.0 specification] [#jsf] == Build user interfaces for web application with JavaServer Faces (JSF) 2.3 Take advantage of the latest JSF features and enhancements. The `jsf-2.3` feature pulls in the Apache MyFaces implementation and integrates it into the Liberty runtime. The new JSF 2.3 capabilities include: * `` * Enhanced component search facility * DataModel implementions can be registered * CDI replacement for `@ManagedProperty` * UIData and `` support for Map and Iterable * `` condition check * Java Time support * WebSocket integration using `` * Multi-field validation using `` * Use CDI for evaluation of JSF-specific Expression Language implicit objects * Support `@Inject` on JSF-specific artifacts * Ajax Method Invocation. See vdldoc for `` * Add `PartialViewContext.getEvalScripts()` method which returns a mutable list of scripts With the delivery of JSF 2.3 you can also use your own JSF 2.3 implementation using the `jsfContainer-2.3` feature. To enable the JSF 2.3 feature, add the feature definition to your `server.xml`: [source,xml] ---- jsf-2.3 ---- The CDI 2.0 feature is now available (`cdi-2.0`) and should be used with the `jsf-2.3` feature. For more info: * https://www.ibm.com/support/knowledgecenter/en/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/twlp_config_jsf23.html[JSF 2.3 (Knowledge Center)] * https://myfaces.apache.org/[Apache MyFaces] * https://javaee.github.io/javaserverfaces-spec/[JSF 2.3 spec] [#jpa] == Interact with databases with Java Persistence API (JPA) 2.2 Java 8 introduced a new Date and Time API, which is more powerful than the old APIs part of java.util for years. Collection streaming, introduced in Java 8, is now formally supported by the JPA 2.2 specification, enabling new ways to process query result sets. Many JPA annotations are now repeatable, eliminating the need to use grouping annotations. To enable the JPA 2.2 feature, add the feature definition to your `server.xml`: [source,xml] ---- jpa-2.2 ---- This enables JPA 2.2 and the EclipseLink 2.7 JPA persistence provider that is bundled with the feature. If you prefer to use your own EclipseLink 2.7 binaries, you can instead enable the `jpaContainer-2.2` feature, which provides JPA 2.2 container integration but does not enable the provided EclipseLink JPA provider implementation. Examples of JPA 2.2 Enhancements: === @Repeatable Annotations Before JPA 2.2: [source,java] ---- @PersistenceContexts( @PersistenceContext(name=“foo”, unitName=“bar”), @PersistenceContext(name=“cloud”, unitName=“sky”)) @Stateless public class SomeEJB { … ---- With JPA 2.2: [source,java] ---- @PersistenceContext(name=“foo”, unitName=“bar”), @PersistenceContext(name=“cloud”, unitName=“sky”) @Stateless public class SomeEJB { … ---- === JPA 2.2 Supports java.time Types [source,java] ---- @Entity public class MyEntity { … // The following map to database time column types natively now @Basic private java.time.LocalDate localDate; @Basic private java.time.LocalDateTime localDateTime; @Basic private java.time.LocalTime localTime; @Basic private java.time.OffsetTime offsetTime; @Basic private java.time.OffsetDateTime offsetDateTime; … } ---- === Attribute Converter classes now support CDI bean injection [source,java] ---- @Converter public class B2IConverter implements AttributeConverter { final static Integer FALSE = new Integer(0); final static Integer TRUE = new Integer(1); @Inject private MyLogger logger; @Override public Integer convertToDatabaseColumn(Boolean b) { Integer i = b ? TRUE : FALSE; logger.log("Convert: " + b + " -> " + i); return i; } @Override public Boolean convertToEntityAttribute(Integer i) { Boolean b = TRUE.equals(s) ? Boolean.TRUE : Boolean.FALSE; logger.log("Convert: " + i + " -> " + b); return b; } } ---- === Method Stream getResultStream() added to Query and TypedQuery interfaces [source,java] ---- @Stateless public class SBean { @PersistenceContext(unitName=“Personnel”) EntityManager em; public int getEmployeeSalaryBudget(int deptId) { final AtomicInteger salBudget = 0; TypedQuery q = em.createQuery(”SELECT e FROM Employee e WHERE e.deptId = :deptId”, Employee.class); q.setParameter(“deptId”, deptId); Stream empStream = q.getResultStream(); empStream.forEach( t -> salBudget.set(salBudget.get() + t.getSalary())); return salBudget.get(); } } ---- For more info: * https://github.com/javaee/jpa-spec/blob/master/jsr338-MR/JavaPersistence.pdf[JPA spec] * https://javadoc.io/doc/org.eclipse.persistence/javax.persistence/2.2.0-RC1[Javadoc] * https://www.eclipse.org/eclipselink/[EclipseLink] [#jwtsso] == Emit and consume JWT cookies with JWT Single Sign-on 1.0 Java Web Tokens (JWT) single sign-on (SSO) cookies can replace proprietary LTPA cookies in many scenarios. They offer improved interoperability and simplified use compared to LTPA cookies in heterogenous and microservice environments. In microservice environments, the self-contained nature of JWT means consuming services don't need to contact an LDAP server or other identity provider in order to complete authentication and authorization. In heterogenous environments, the standards-based JWT is usable across multiple implementations where the proprietary WebSphere LTPA cookie is not. JSON Web Key (JWK) can be used for key retrieval to simplify key management. To enable JWT SSO so that Liberty emits and consumes JWT cookies instead of LTPA cookies, add the definition to your `server.xml`: [source,xml] ---- JwtSso-1.0 ---- For more info: * https://www.ibm.com/support/knowledgecenter/SSAW57_liberty/com.ibm.websphere.wlp.nd.multiplatform.doc/ae/twlp_sec_config_jwt_sso.html[Configuring JWT SSO (Knowledge Center)] [#appsecurity] == Secure your applications using Security API 1.0 specification The `appSecurity-3.0` feature provides support for the Java EE Security API 1.0 specification. The https://jcp.org/en/jsr/detail?id=375"[Java Specification Request (JSR) 375] specifies the requirement. The specification promotes self-contained application security portability across all Java EE servers, and makes use of modern programming concepts such as expression language and context dependency injection (CDI). It defines annotations specific to various authentication mechanisms, identity stores to handle user authentication, and common programming API to do programmatic Java EE security. It reduces the dependency on the deployment descriptors and application server based configuration for securing Java EE web resources. Once you configure the `appSecurity-3.0` feature, your application can annotate the authentication mechanisms and the identity stores that are needed. The applications can provide their own implementations to replace the application server provided ones. For example, you can create a custom authentication mechanism that you can bundle in your web application without the need to configure the `login-config` element in the `web.xml` file with one of the predefined auth-method types. If you also include your own `IdentityStore` bean in your application, your IdentityStore can be used to verify the user credentials without the need to configure a user registry in the `server.xml`. The applications can also use the SecurityContext API defined in the specification to perform programmatic security checks. To enable the Security API 1.0 feature, add the feature definition to your `server.xml`: [source,xml] ---- appSecurity-3.0 ---- For more info: * https://www.ibm.com/support/knowledgecenter/SSAW57_liberty/com.ibm.websphere.wlp.nd.multiplatform.doc/ae/rwlp_sec_jee_api.html[Security API 1.0 (Knowledge Center)] [#jsonb] == Store JSON with JSON-B 1.0 JSONB provides a structured format for storing JSON. The jsonb-1.0 feature provides a preview of the JSON Binding (JSON-B) 1.0 specification interfaces, as well as the reference implementation (Eclipse Yasson). JSON technology has proven to be a powerful tool in modern Java EE applications, especially when using a microservices-oriented architecture. Traditionally applications had to provide their own JSON binding implementations and package them in a shared library or application. With the jsonb-1.0 feature, the specification interfaces and implementation are provided out of the box, ready to be used directly by applications. To enable the JSON-B 1.0 feature, add the feature definition to your `server.xml`: [source,xml] ---- jsonb-1.0 ---- For more info: * http://json-b.net/[JSON-B.net] [#beanval] == Validate objects, parameters, and more with Bean Validation 2.0 With the bean validation 2.0 feature, Liberty is using Hibernate Validator as its bean validation implementation. Previously for bean validation 1.0 and 1.1 we used Apache Validator. To enable the bean validation 2.0 feature, add the feature definition to your `server.xml`: [source,xml] ---- beanValidation-2.0 ---- For more info: * http://hibernate.org/validator/[Hibernate website] [#spring] == Deploy Spring Boot applications to Liberty Liberty now supports deploying Spring Boot application uber (or fat) JARs without requiring them to be repackaged as a WAR. Additional tools are provided to manage and separate the embedded dependencies of a Spring Boot application in order to provide more efficient deployments using Docker. When a Spring Boot application is deployed the Liberty web container is used instead of the embedded server packaged with the Spring Boot application, for example Tomcat, Jetty or Undertow. To give it a try, add `springBoot-1.5` or `springBoot-2.0` to the feature list in the `server.xml`. Most Spring Boot applications also require a Servlet feature to be enabled (either `servlet-3.1` or `servlet-4.0`). You can also add features for WebSocket support (`websocket-1.0` or `websocket-1.1`), JSP support (`jsp-2.3`), and HTTPS support (`transportSecurity-1.0`). For example: [source,xml] ---- springBoot-2.0 servlet-4.0 websocket-1.1 jsp-2.3 transportSecurity-1.0 ---- Deploy your Spring Boot applications to liberty in one of the following ways: * Place the Spring Boot application JAR in the server's `dropins/spring/` folder (e.g. `dropins/spring/myapp.jar`) or directly in the `dropins/` folder and using the `.spring` extension (e.g. `dropins/myapp.jar.spring`). * Place the Spring Boot application JAR in the server's `apps/` folder and add a `` element to the `server.xml` (e.g. ``). For more info, see: * https://www.ibm.com/support/knowledgecenter/en/SSEQTP_liberty/com.ibm.websphere.wlp.doc/ae/rwlp_springboot.html[Spring Boot support (Knowledge Center)] * https://spring.io/projects/spring-boot[Spring Boot project] [#certmapper] == Control how custom X.509 certificates map to users in Liberty's LDAP and basic user registries You now have complete control over how certificates are mapped to users in the user registry. The out-of-the-box X.509 certificate mappers for the LDAP user registry did not handle custom OID's, parsing of certificate fields and included custom filtering of only a subset of the certificate's fields. For example, there was no support for Subject Alternative Name (SAN). The out-of-the-box X.509 certificate mapper for the basic user registry only supported using the subject's `cn` RDN for the user name. With the X509CertificateMapper API, you can now map a X.509 certificate to a user in the user registry in any way that is required. === Enabling the custom mapping using the BELLs feature Implement the `com.ibm.websphere.security.X509CertificateMapper` interface and include it in a JAR. Also include in the JAR a Java ServiceLoader provider configuration file (`META-INF/com.ibm.websphere.security.X509CertificateMapper`) that contains the fully-qualified class names of any X509CertificateMapper implementations to be used in the Liberty server. Each implementation must be preceded by a comment line containing a key-value pair containing the key `x509.certificate.mapper.id` and a unique ID as the value. Use this ID to reference the implementation from the `server.xml` configuration file. Load these implementations into Liberty's classpath using the `bells-1.0` feature and a shared library. Example configuration file entry: [source,xml] ---- # x509.certificate.mapper.id=basicMapper com.mycompany.BasicMapper # x509.certificate.mapper.id=ldapMapper com.mycompany.LdapMapper ---- Example `server.xml` configuration for two separate X509CertificateMapper implementations to a basic and LDAP user registry: [source,xml] ---- basicRegistry-1.0 ldapRegistry-3.0 bells-1.0 ---- === Enabling the custom mapping with a user feature Implement the `com.ibm.websphere.security.X509CertificateMapper` interface and include it in the user feature bundle. Define the X509CertificateMapper implementations as Service Components. The Service Component must specify the `x509.certificate.mapper.id` property which defines a unique ID as the value. The property can either be specified manually in the Service Component XML file or using the property field of the Component annotation. Load these implementations into Liberty's classpath with the user feature. Use this ID to reference the implementation from the `server.xml` configuration file. Example `server.xml` configuration for configuring two separate X509CertificateMapper implementations to a basic and LDAP user registry: [source,xml] ---- basicRegistry-1.0 ldapRegistry-3.0 usr:myFeature-1.0 ---- For more info: * https://www.ibm.com/support/knowledgecenter/SSAW57_liberty/com.ibm.websphere.wlp.nd.multiplatform.doc/ae/rwlp_sec_basic_certmap.html[Basic registry mapping (Knowledge Center)] * https://www.ibm.com/support/knowledgecenter/SSAW57_liberty/com.ibm.websphere.wlp.nd.multiplatform.doc/ae/rwlp_sec_ldap_certmap.html[LDAP registry mapping (Knowledge Center)] ## Ready to give it a try? [link=https://openliberty.io/downloads/] image::img/blog/blog_btn_download-ol.svg[Download Open Liberty, align="center", role="download-ol-button"] [link=https://stackoverflow.com/tags/open-liberty] image::img/blog/blog_btn_stack.svg[Ask a question on Stack Overflow, align="center"]