diff --git oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/AnnotatedStandardMBean.java oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/AnnotatedStandardMBean.java
new file mode 100644
index 0000000..1b18602
--- /dev/null
+++ oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/AnnotatedStandardMBean.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.commons.jmx;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.NotCompliantMBeanException;
+import javax.management.StandardMBean;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.Invokable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The extension of {@link javax.management.StandardMBean} that will automatically provide JMX
+ * metadata through annotations.
+ *
+ * @see javax.management.MBeanInfo
+ * @see Description
+ * @see Name
+ * @see Impact
+ */
+public class AnnotatedStandardMBean extends StandardMBean {
+    private static final Logger log = LoggerFactory.getLogger(AnnotatedStandardMBean.class);
+
+    private static final Map<String, Class<?>> PRIMITIVES = ImmutableMap.<String, Class<?>>builder()
+            .put("boolean", boolean.class)
+            .put("byte", byte.class)
+            .put("short", short.class)
+            .put("int", int.class)
+            .put("long", long.class)
+            .put("float", float.class)
+            .put("double", double.class)
+            .build();
+
+    /**
+     * Make a DynamicMBean out of the object implementation, using the specified
+     * mbeanInterface class.
+     *
+     * @see {@link javax.management.StandardMBean#StandardMBean(Object, Class)}
+     */
+    public <T> AnnotatedStandardMBean(T implementation, Class<T> mbeanInterface)
+            throws NotCompliantMBeanException {
+        super(implementation, mbeanInterface);
+    }
+
+    protected AnnotatedStandardMBean(Class<?> mbeanInterface)
+            throws NotCompliantMBeanException {
+        super(mbeanInterface);
+    }
+
+    @Override
+    protected String getDescription(MBeanInfo info) {
+        Description res = (Description) getMBeanInterface().getAnnotation(Description.class);
+        return res == null ? super.getDescription(info) : res.value();
+    }
+
+    @Override
+    protected MBeanConstructorInfo[] getConstructors(
+            MBeanConstructorInfo[] ctors, Object impl) {
+        MBeanConstructorInfo[] results = new MBeanConstructorInfo[ctors.length];
+
+        for (int i = 0; i < ctors.length; i++) {
+            MBeanConstructorInfo info = ctors[i];
+            try {
+                String name = info.getName();
+
+                Constructor<?> c = getConstructor(info);
+
+                Description res = c.getAnnotation(Description.class);
+                String desc = res == null
+                        ? super.getDescription(info)
+                        : res.value();
+
+                results[i] = new MBeanConstructorInfo(name, desc,
+                        info.getSignature());
+            } catch (Exception e) {
+                log.warn("Error accessing bean constructor", e);
+                results[i] = info;
+            }
+        }
+
+        return results;
+    }
+
+    @Override
+    protected String getParameterName(MBeanConstructorInfo ctor,
+                                      MBeanParameterInfo param, int sequence) {
+        Name desc = getParamAnnotation(ctor, sequence, Name.class);
+
+        return desc == null
+                ? super.getParameterName(ctor, param, sequence)
+                : desc.value();
+    }
+
+    @Override
+    protected String getDescription(MBeanConstructorInfo ctor,
+                                    MBeanParameterInfo param, int sequence) {
+        Description desc = getParamAnnotation(ctor, sequence, Description.class);
+
+        return desc == null
+                ? super.getDescription(ctor, param, sequence)
+                : desc.value();
+    }
+
+    @Override
+    protected String getDescription(MBeanAttributeInfo info) {
+        try {
+            Description desc = getAnnotation(info, Description.class);
+            return desc == null ? super.getDescription(info) : desc.value();
+        } catch (Exception e) {
+            log.warn("Error accessing bean method", e);
+            return super.getDescription(info);
+        }
+    }
+
+    @Override
+    protected String getDescription(MBeanOperationInfo info) {
+        try {
+            Description res = getMethod(info).getAnnotation(Description.class);
+            return res == null ? super.getDescription(info) : res.value();
+        } catch (Exception e) {
+            log.warn("Error accessing bean method", e);
+            return super.getDescription(info);
+        }
+    }
+
+    @Override
+    protected int getImpact(MBeanOperationInfo info) {
+        try {
+            Impact res = getMethod(info).getAnnotation(Impact.class);
+            return res == null ? super.getImpact(info) : res.value();
+        } catch (Exception e) {
+            log.warn("Error accessing bean method", e);
+            return super.getImpact(info);
+        }
+    }
+
+    @Override
+    protected String getParameterName(MBeanOperationInfo op,
+                                      MBeanParameterInfo param, int sequence) {
+        Name desc = getParamAnnotation(op, sequence, Name.class);
+
+        return desc == null
+                ? super.getParameterName(op, param, sequence)
+                : desc.value();
+    }
+
+    @Override
+    protected String getDescription(MBeanOperationInfo op,
+                                    MBeanParameterInfo param, int sequence) {
+        Description desc = getParamAnnotation(op, sequence, Description.class);
+
+        return desc == null
+                ? super.getDescription(op, param, sequence)
+                : desc.value();
+    }
+
+    private Constructor<?> getConstructor(MBeanConstructorInfo info)
+            throws ClassNotFoundException, NoSuchMethodException {
+        MBeanParameterInfo[] sig = info.getSignature();
+
+        Class<?>[] params = new Class<?>[info.getSignature().length];
+        for (int i = 0; i < sig.length; i++) {
+            MBeanParameterInfo p = sig[i];
+            params[i] = loadClass(p.getType(),
+                    getMBeanInterface().getClassLoader());
+        }
+
+        return getImplementationClass().getConstructor(params);
+    }
+
+    private <T extends Annotation> T getAnnotation(MBeanAttributeInfo info,
+                                                   Class<T> a) throws NoSuchMethodException, ClassNotFoundException {
+        if (info.isReadable()) {
+            T res = getReadMethod(info).getAnnotation(a);
+
+            if (res != null) {
+                return res;
+            }
+
+            if (!info.isWritable()) {
+                return null;
+            }
+        }
+
+        return getWriteMethod(info).getAnnotation(a);
+    }
+
+    private <T extends Annotation> T getParamAnnotation(
+            MBeanConstructorInfo ctor, int sequence, Class<T> clazz) {
+        try {
+            Constructor<?> c = getConstructor(ctor);
+            return Invokable.from(c).getParameters().get(sequence).getAnnotation(clazz);
+        } catch (Exception e) {
+            log.warn("Error accessing bean method", e);
+            return null;
+        }
+    }
+
+    private <T extends Annotation> T getParamAnnotation(
+            MBeanOperationInfo op, int sequence, Class<T> clazz) {
+        try {
+            Method m = getMethod(op);
+            return Invokable.from(m).getParameters().get(sequence).getAnnotation(clazz);
+        } catch (Exception e) {
+            log.warn("Error accessing bean method", e);
+            return null;
+        }
+    }
+
+    private Method getReadMethod(MBeanAttributeInfo info)
+            throws NoSuchMethodException {
+        if (info.isIs()) {
+            return getMBeanInterface().getMethod("is" + info.getName(),
+                    new Class<?>[0]);
+        }
+
+        return getMBeanInterface().getMethod("get" + info.getName(),
+                new Class<?>[0]);
+    }
+
+    private Method getWriteMethod(MBeanAttributeInfo info)
+            throws NoSuchMethodException, ClassNotFoundException {
+        return getMBeanInterface().getMethod(
+                "set" + info.getName(),
+                new Class<?>[]{loadClass(info.getType(),
+                        getMBeanInterface().getClassLoader())}
+        );
+    }
+
+    private Method getMethod(MBeanOperationInfo info)
+            throws ClassNotFoundException, NoSuchMethodException {
+        MBeanParameterInfo[] sig = info.getSignature();
+
+        Class<?>[] params = new Class<?>[info.getSignature().length];
+        for (int i = 0; i < sig.length; i++) {
+            MBeanParameterInfo p = sig[i];
+            params[i] = loadClass(p.getType(),
+                    getMBeanInterface().getClassLoader());
+        }
+
+        return getMBeanInterface().getMethod(info.getName(), params);
+    }
+
+    private static Class<?> loadClass(String type, ClassLoader loader)
+            throws ClassNotFoundException {
+        if (PRIMITIVES.containsKey(type)) {
+            return PRIMITIVES.get(type);
+        }
+
+        return loader.loadClass(type);
+    }
+}
diff --git oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/Description.java oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/Description.java
new file mode 100644
index 0000000..b1ec942
--- /dev/null
+++ oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/Description.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.commons.jmx;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Produces a description that will be used by JMX metadata.
+ */
+@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD,
+        ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface Description {
+    String value() default "";
+}
diff --git oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/Impact.java oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/Impact.java
new file mode 100644
index 0000000..55236e2
--- /dev/null
+++ oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/Impact.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.commons.jmx;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Produces an operation impact that will be returned by JMX
+ * {@link javax.management.MBeanOperationInfo#getImpact()}.
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface Impact {
+    /**
+     * One of {@link javax.management.MBeanOperationInfo#ACTION},
+     * {@link javax.management.MBeanOperationInfo#ACTION_INFO},
+     * {@link javax.management.MBeanOperationInfo#INFO},
+     * {@link javax.management.MBeanOperationInfo#UNKNOWN}.
+     */
+    int value();
+}
diff --git oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/Name.java oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/Name.java
new file mode 100644
index 0000000..c6f984f
--- /dev/null
+++ oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/Name.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.commons.jmx;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Produces a parameter name that will be returned by JMX
+ * {@link javax.management.MBeanParameterInfo#getName()}.
+ */
+@Target({ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface Name {
+    String value();
+}
diff --git oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/package-info.java oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/package-info.java
new file mode 100644
index 0000000..2c57af9
--- /dev/null
+++ oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/jmx/package-info.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Provides annotation support to produce JMX metadata.
+ *
+ * @version 1.0
+ */
+@Version("1.0")
+@Export(optional = "provide:=true")
+package org.apache.jackrabbit.oak.commons.jmx;
+
+import aQute.bnd.annotation.Export;
+import aQute.bnd.annotation.Version;
+
diff --git oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/jmx/AnnotatedStandardMBeanTest.java oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/jmx/AnnotatedStandardMBeanTest.java
new file mode 100644
index 0000000..9dd67f5
--- /dev/null
+++ oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/jmx/AnnotatedStandardMBeanTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.commons.jmx;
+
+import java.lang.management.ManagementFactory;
+
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import junit.framework.Assert;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+
+public class AnnotatedStandardMBeanTest {
+    protected static ObjectName objectName;
+
+    protected static MBeanServer server;
+
+    @BeforeClass
+    public static void init() throws Exception {
+        AnnotatedStandardMBean object = new AnnotatedStandardMBean(new Foo(""),
+            FooMBean.class);
+
+        objectName = new ObjectName("abc:TYPE=Test");
+
+        server = ManagementFactory.getPlatformMBeanServer();
+        server.registerMBean(object, objectName);
+    }
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        server.unregisterMBean(objectName);
+    }
+
+    @Test
+    public void test() throws Exception {
+        MBeanInfo info = server.getMBeanInfo(objectName);
+
+        assertEquals("MBean desc.", info.getDescription());
+
+        MBeanConstructorInfo c0 = info.getConstructors()[0];
+        assertEquals("const", c0.getDescription());
+
+        MBeanParameterInfo c0p = c0.getSignature()[0];
+        assertEquals("const1", c0p.getName());
+        assertEquals("const1desc", c0p.getDescription());
+
+        MBeanAttributeInfo a0 = findAttribute(info, "Getter");
+        assertEquals("getter", a0.getDescription());
+
+        MBeanAttributeInfo a1 = findAttribute(info, "It");
+        assertEquals("is", a1.getDescription());
+
+        MBeanAttributeInfo a2 = findAttribute(info, "Setter");
+        assertEquals("setter", a2.getDescription());
+
+        MBeanOperationInfo op0 = info.getOperations()[0];
+        assertEquals("run", op0.getDescription());
+        assertEquals(MBeanOperationInfo.INFO, op0.getImpact());
+
+        MBeanParameterInfo p0 = op0.getSignature()[0];
+        assertEquals("timeout", p0.getName());
+        assertEquals("how long?", p0.getDescription());
+    }
+
+    private MBeanAttributeInfo findAttribute(MBeanInfo info, String name) {
+        for (MBeanAttributeInfo a : info.getAttributes()) {
+            if (a.getName().equals(name)) return a;
+        }
+
+        return null;
+    }
+
+    @Description("MBean desc.")
+    public interface FooMBean {
+        @Description("getter")
+        String getGetter();
+
+        @Description("is")
+        boolean isIt();
+
+        @Description("setter")
+        void setSetter(long s);
+
+        @Description("run")
+        @Impact(MBeanOperationInfo.INFO)
+        void run(@Name("timeout") @Description("how long?") long timeout);
+    }
+
+    public static class Foo implements FooMBean {
+        @Description("const")
+        public Foo(@Name("const1") @Description("const1desc") String s) {
+        }
+
+        public String getGetter() {
+            return null;
+        }
+
+        public boolean isIt() {
+            return false;
+        }
+
+        public void setSetter(long s) {
+        }
+
+        public void run(long timeout) {
+        }
+    }
+}
